package com.xyzwps.collections.list;

import com.xyzwps.collections.List;
import com.xyzwps.collections.Utils;

import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.Objects;
import java.util.RandomAccess;

/**
 * 这是一个基于数组的 List 实现。
 *
 * @param <E> 列表中的元素类型。
 */
public class ArrayList<E> implements List<E>, RandomAccess {

    private static final int DEFAULT_CAPACITY = Utils.ARRAY_BASED_COLLECTION_DEFAULT_CAPACITY;
    private E[] data;
    private int size;
    private int modi = 0;

    public ArrayList() {
        this(DEFAULT_CAPACITY);
    }

    public ArrayList(final int capacity) {
        checkCapacity(capacity);
        this.data = Utils.newArray(capacity);
        this.size = 0;
    }

    private static final int MIN_CAPACITY = 1;

    private void checkCapacity(int capacity) {
        if (capacity < MIN_CAPACITY) {
            throw new IllegalArgumentException("Capacity CANNOT be less than " + MIN_CAPACITY + ".");
        }
    }

    @Override
    public void addAll(E[] elements) {
        Objects.requireNonNull(elements);
        if (elements.length == 0) return;

        this.modi++;
        final int leftCapacity = this.data.length - this.size;
        if (leftCapacity < elements.length) {
            this.data = Utils.enlarge(this.data, elements.length);
        }
        System.arraycopy(elements, 0, this.data, this.size, elements.length);
        this.size += elements.length;
    }

    @Override
    public void add(E element) {
        this.modi++;

        final int leftCapacity = this.data.length - this.size;
        if (leftCapacity <= 0) {
            this.data = Utils.enlarge(this.data);
        }
        this.data[this.size++] = element;
    }

    @Override
    public E getAt(Integer index) {
        checkIndex(index);
        return this.data[index];
    }

    private void checkIndex(int index) {
        if (index < 0 || index >= this.size()) {
            throw new IndexOutOfBoundsException("Index " + index + " out of bounds");
        }
    }


    @Override
    public void putAt(Integer index, E element) {
        this.modi++;
        this.checkIndex(index);
        this.data[index] = element;
    }

    @Override
    public void insertAt(int index, E element) {
        this.modi++;
        this.checkIndex(index);

        if (this.size() == this.data.length) {
            this.data = Utils.enlarge(this.data);
        }

        final int moveSize = this.size() - index;
        System.arraycopy(this.data, index, this.data, index + 1, moveSize);
        this.data[index] = element;
        this.size++;
    }

    @Override
    public E removeAt(int index) {
        this.modi++;
        checkIndex(index);

        final E result = this.data[index];
        if (index != this.size() - 1) {
            final int moveSize = this.size() - index - 1;
            System.arraycopy(this.data, index + 1, this.data, index, moveSize);
        }
        this.size--;
        this.data[this.size] = null;
        this.tryShrink();
        return result;
    }

    @Override
    public int removeFirst(E element) {
        this.modi++;
        final int index = this.indexOf(element);
        if (index > -1) {
            this.removeAt(index);
        }
        return index;
    }

    @Override
    public int indexOf(E element) {
        for (int i = 0; i < this.size; i++) {
            if (Objects.equals(this.data[i], element)) return i;
        }
        return -1;
    }

    private void tryShrink() {
        final int currentCapacity = this.data.length;
        if (this.size() > DEFAULT_CAPACITY && this.size < (currentCapacity >> 2)) {
            this.data = Utils.shrink(this.data, this.size());
        }
    }

    @Override
    public void clear() {
        this.modi++;
        this.data = Utils.newArray(DEFAULT_CAPACITY);
        this.size = 0;
    }

    @Override
    public boolean has(E element) {
        return indexOf(element) > -1;
    }

    @Override
    public int size() {
        return this.size;
    }

    @Override
    public Iterator<E> iterator() {
        return new Itr<>(this);
    }

    private static class Itr<E> implements Iterator<E> {
        int curr = -1;
        final int expectedModi;
        ArrayList<E> list;

        Itr(ArrayList<E> list) {
            this.list = list;
            this.expectedModi = list.modi;
        }

        @Override
        public boolean hasNext() {
            this.checkForComodification();
            return curr + 1 < list.size() - 1;
        }

        @Override
        public E next() {
            this.checkForComodification();
            return list.getAt(++curr);
        }

        final void checkForComodification() {
            if (expectedModi != list.modi)
                throw new ConcurrentModificationException();
        }
    }
}
